/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package client;

import commom.Network;
import commom.VirtualMachine;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.UUID;
import org.libvirt.Connect;
import org.libvirt.Domain;
import org.libvirt.LibvirtException;
import server.Slave;
import simulation.VMTask;
import util.DistributionFactory;
import util.Logger;
import virt.VirtualMachineFactory;
import xml.Parser;

/**
 * This class receives an open socket, by that
 * open sock is gonna come a xml file with some
 * directions to follow.
 *
 * Obs1: The vms ArrayList keeps jumping from one
 * function to another, is good to think on a better
 * way to deal with that. Maybe makes him in a static
 * attribute from somithing could be the answer.
 *
 * Obs2: Be aware with the concurrency regarding the
 * vms ArrayList, make a syncronized function to deal
 * with de xml action could be a reasonable solution.
 * @author gravena
 */
public class DoAction implements Runnable {

    ArrayList<VirtualMachine> vms;
    ArrayList<TestSlave> test;
    Socket sock;

    public DoAction(ArrayList<VirtualMachine> vms, ArrayList<TestSlave> test, Socket sock) {
        this.vms = vms;
        this.test = test;
        this.sock = sock;
    }

    @Override
    public void run() {
        /* Receive the request and returns its XML */
        String xml = Network.receiveXML(sock);

        try {
            Parser parser = new Parser(xml); /* Open a Parser instance for the request */
            DataOutputStream out = new DataOutputStream(this.sock.getOutputStream()); /* Opens an output channel to send the response */

            String messageType = parser.getMessageType(); /* Get the message type of the request, this types defines the behavior of the response */
            System.out.println("Requested Operation: " + messageType);
            /* Parses the request XML and based on the action requested do something */
            if (messageType.equals("SlaveCheckSum")) { /* Calculates the checksum of the slave and sends to the master */
                String responseXML = "<message type=\"SlaveCheckSumResponse\">\n<checksum>" + Slave.getCheckSum(vms) + "</checksum>\n</message>";
                out.writeInt(responseXML.length());
                out.write(responseXML.getBytes());
            } else if (messageType.equals("VirtualMachineList")) { /* Returns the virtual machine list to the master */
                String responseXML = "<message type=\"VirtualMachineListResponse\">\n";
                for (int i = 0; i < vms.size(); i++) {
                    responseXML += "<VM>\n";
                    responseXML += "<UUID>" + vms.get(i).getUUID() + "</UUID>\n";
                    responseXML += "<ip>" + vms.get(i).getIp() + "</ip>\n";
                    responseXML += "<port>" + vms.get(i).getPort() + "</port>\n";
                    responseXML += "<name>" + vms.get(i).getName() + "</name>\n";
                    responseXML += "<active>" + vms.get(i).isActive() + "</active>\n";
                    responseXML += "<available>" + vms.get(i).isAvailabe() + "</available>\n";
                    responseXML += "</VM>\n";
                }
                responseXML += "</message>";
                out.writeInt(responseXML.length());
                out.write(responseXML.getBytes());
            } else if (messageType.equals("CreateVM")) { /* Try to create a new virtual machine */
                try {
                    String responseXML = "<message type=\"CreateVMResponse\">\n<status>OK</status>\n</message>";
                    // It's mandatory to implement some VM creation policy here
                    Domain createVM = VirtualMachineFactory.createVM(parser.getVMId()); // Create and starts the VM
                    VirtualMachine VM = new VirtualMachine("some ip", Network.DEFAULT_PORT_VM, "some ip" + System.nanoTime(), true, true); // Register on the list
                    VM.setId(createVM.getID()); // Store the VM ID, used on the destroy operation
                    VM.setUUID(parser.getVMId());
                    System.out.println("Create: " + parser.getVMId());
                    this.vms.add(VM);
                    out.writeInt(responseXML.length());
                    out.write(responseXML.getBytes());
                } catch (LibvirtException ex) {
                    String responseXML = "<message type=\"CreateVMResponse\">\n<status>Failure</status>\n</message>";
                    out.writeInt(responseXML.length());
                    out.write(responseXML.getBytes());
                }
            } else if (messageType.equals("DestroyVM")) {
                UUID uuid = parser.getVMId();
                int i;

                System.out.println("Destroy: " + parser.getVMId());
                for (i = 0; i < this.vms.size(); i++) {
                    if(uuid.compareTo(this.vms.get(i).getUUID()) == 0) {
                        break;
                    }                    
                }

                if (i < this.vms.size()) {
                    Connect conn;
                    try {
                        conn = new Connect("qemu:///system");
                        Domain domain = conn.domainLookupByID(this.vms.get(i).getId());
                        
                        domain.destroy();
                        this.vms.remove(i);
                        String responseXML = "<message type=\"DestroyVMResponse\">\n<status>OK</status>\n</message>";
                        out.writeInt(responseXML.length());
                        out.write(responseXML.getBytes());
                    } catch (LibvirtException ex) {
                        System.out.println("Erro Libvirt");
                        String responseXML = "<message type=\"DestroyVMResponse\">\n<status>Failure</status>\n</message>";
                        out.writeInt(responseXML.length());
                        out.write(responseXML.getBytes());
                    }

                } else {
                    String responseXML = "<message type=\"DestroyVMResponse\">\n<status>Failure</status>\n</message>";
                    out.writeInt(responseXML.length());
                    out.write(responseXML.getBytes());
                }

            } else if (messageType.equals("TestSpec")) { /* Receives a test specification */
                String responseXML = "<message type=\"TestSpecResponse\"><status>OK</status></message>";

                // Se o teste for destinado a uma máquina virtual, mande para ela, se não
                // Execute aqui mesmo
                
                UUID testID = parser.getTestID(); /* Retrieve the testUUID from the message */
                
                TestSlave temp = TestSlave.searchUUID(test, testID); /* Search for the UUID in the test list */
                double param[] = parser.gerParameters();             /* Get the distribution parameter list from the message */ 
                int samples = parser.getSamples(); /* Number of test repetitions */
                TestSpecSlave new_testSpec = new TestSpecSlave(parser.getVMId(), new VMTask(parser.getBinary(), null, DistributionFactory.getInstance(parser.getDistribution(), param),samples), testID); /* Build a test specification object based on the received parameters */
                if(temp == null) { // If the test does not exist locally yet 
                    TestSlave new_test = new TestSlave(testID); /* Create a new test specification */                   
                    /* Add specifications here */
                    new_test.addTestSpec(new_testSpec); /* Add the test specification to the test list */
                    
                    test.add(new_test);
                    temp = new_test;
                } else { // If the test alredy exist locally 
                    if(!temp.getSpecList().contains(new_testSpec)) {
                        temp.addTestSpec(new_testSpec);
                    }
                }
                
                /* Delete this after all works out */
                for(int i=0;i<temp.getSpecList().size();i++) {
                    System.out.println(temp.getSpecList().get(i).getId());
                    System.out.println(temp.getSpecList().get(i).getVm());
                    System.out.println(temp.getSpecList().get(i).getTask());
                    System.out.println();
                }
                
                out.writeInt(responseXML.length());
                out.write(responseXML.getBytes());
            } else if (messageType.equals("SendBinary")) {
                String responseXML = "<message type=\"SendBinaryResponse\"><status>OK</status></message>";
                
                File file = new File("binaries/" + parser.getFileName());

                /* If the file creation was successfull, sends a OK to the master and waits for the byte stream */
                file.createNewFile();                
                out.writeInt(responseXML.length());
                out.write(responseXML.getBytes());
                

                /* Accepts the new connection from the master */
                /* Maybe turn this code into a separated thread, because if somebody opens a stream and does not indeed send a binary
                 * this will lead to a deadlock here, making the service unavailable.
                 * So this is a major security flaw.
                 */                
                ServerSocket wait = new ServerSocket(Network.DEFAULT_FILE_TRANSFER_PORT);
                Socket receive = wait.accept();
                DataInputStream inputStream = new DataInputStream(receive.getInputStream());
                FileOutputStream outputStream = new FileOutputStream(file);

                /* Reads the byte stream from the socket and write it on the file */
                byte b[] = new byte[Integer.valueOf(parser.getFileSize())];
                inputStream.read(b);
                outputStream.write(b);                

                /* Release the resources */
                inputStream.close();
                outputStream.close();
                wait.close();
                receive.close();
                System.out.println("Gravou");
            } else if(messageType.equals("StartTest")) {
                String responseXML = "";
                
                UUID testID = parser.getTestID();
                
                TestSlave testSlave = TestSlave.searchUUID(test, testID);
                
                if(testSlave == null) {
                    responseXML  = "<message type=\"StartTestResponse\"><status>FAILURE</status><timestamp>" + System.currentTimeMillis() + "</timestamp></message>";
                } else {
                    responseXML  = "<message type=\"StartTestResponse\"><status>OK</status><timestamp>" + System.currentTimeMillis() + "</timestamp></message>";
                    
                    for(int i=0;i<testSlave.getSpecList().size();i++) {
                        TestSpecSlave testSpecSlave = testSlave.getSpecList().get(i);
                        
                        byte[] addr = new byte[4];
                        addr[0] = 127;
                        addr[1] = 0;
                        addr[2] = 0;
                        addr[3] = 1;
        
                        UUID localUUID = UUID.nameUUIDFromBytes(addr);
                        
                        if(testSpecSlave.getVm().compareTo(localUUID) == 0) { // Local execution
                            VMTask task = testSpecSlave.getTask();
                            new Thread(task).start();
                            
                        } else { // Send to the VM
                            
                        }
                    }
                }
                
                out.writeInt(responseXML.length());
                out.write(responseXML.getBytes());
            } else if(messageType.equals("AssocRequest")) {
                String responseXML = "<message type=\"AssocRequestResponse\"><status>OK</status></message>";
                out.writeInt(responseXML.length());
                out.write(responseXML.getBytes());
            }

            this.sock.close();
        } catch (IOException ex) {
            Logger.add(ex.getMessage());
            System.out.println(ex.getMessage());
        }

        /**
         * Do some action here.
         * This probabily will be a function call,
         * with the xml as a parameter, as commented
         * earlier, it's a good idea to make this
         * function syncronized.
         */
    }
}
